﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Configuration;

namespace Zephyris_Space_Mercs_Server
{
    class Program
    {
        private const char LengthPrefixDelimiter = ';';
        private static AutoResetEvent _flipFlop = new AutoResetEvent(false);
        private static Thread serverThread;
        private static Thread cleanupThread;
        private static bool serverRunning = false;
        private static bool verbose = false;

        private static int playersLoggedIn = 0;
        private static Dictionary<IPEndPoint, PlayerObject> connectedPlayers = new Dictionary<IPEndPoint, PlayerObject>();

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                for (int i = 0; i < args.Length; i++)
                {
                    switch (args[i])
                    {
                        case "-start":
                            {
                                if (!serverRunning)
                                {
                                    serverRunning = true;
                                    serverThread = new Thread(StartServer);
                                    serverThread.Start();
                                    cleanupThread = new Thread(CleanUp);
                                    cleanupThread.Start();
                                }

                                break;
                            }

                        case "-verbose":
                            {
                                verbose = true;

                                break;
                            }

                        default:
                            {
                                ShowHelp();
                                break;
                            }
                    }
                }
            }
            else
            {
                ShowHelp();
                return;
            }
        }

        private static void ShowHelp()
        {
            Console.WriteLine("Correct usage is:-");
            Console.WriteLine("-verbose to output details");
            Console.WriteLine("-start to start the server");
            Console.WriteLine(System.Environment.NewLine);
        }

        private static void StartServer()
        {
            if (!verbose) Console.WriteLine(DateTime.Now + " - " + "Server Starting...");

            // connect to DB
            ErrorClass errorClass = DBManager.connectDB(ConfigurationSettings.AppSettings["DatabaseUser"],
                ConfigurationSettings.AppSettings["DatabasePassword"],
                ConfigurationSettings.AppSettings["DatabaseServer"],
                ConfigurationSettings.AppSettings["Database"]);

            Helpers.Logger(errorClass.errorMessage, verbose);

            // create a socket
            Socket listener = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);

            // create a local endpoint
            IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
            IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList.First(),
                int.Parse(ConfigurationSettings.AppSettings["Port"]));

            Helpers.Logger("Local address and port : " + localEP, verbose);
            if (!verbose) Console.WriteLine(DateTime.Now + " - " + "Server Started");

            // bind and listen
            try
            {
                listener.Bind(localEP);
                listener.Listen(5);

                while (serverRunning)
                {
                    Helpers.Logger("Waiting for the next connection...", verbose);

                    // accept the next connection
                    listener.BeginAccept(AcceptCallback, listener);

                    _flipFlop.WaitOne();
                }
            }
            catch (Exception ex)
            {
                Helpers.Logger(ex.ToString(), verbose);
            }

            listener.Accept();
            listener.Listen(1);
        }

        private static void AcceptCallback(IAsyncResult ar)
        {
            // retrieve the listener, accept
            var listener = (Socket)ar.AsyncState;
            var socket = listener.EndAccept(ar);

            Helpers.Logger("Connected a client.", verbose);

            // trigger new listen
            _flipFlop.Set();

            // start receiving
            var state = new StateObject();
            state.Socket = socket;
            socket.BeginReceive(state.Buffer,
                0,
                StateObject.BufferSize,
                0,
                ReceiveCallback,
                state);
        }

        private static void ReceiveCallback(IAsyncResult ar)
        {
            // retrieve the state and socket
            StateObject state = (StateObject)ar.AsyncState;
            Socket socket = state.Socket;

            // Read data from the client socket.
            int read = socket.EndReceive(ar);

            // flag to indicate there's more data coming
            bool allRead = true;

            // Data was read from the client socket.
            if (read > 0)
            {
                // convert result and output
                string chunk = Encoding.UTF8.GetString(state.Buffer, 0, read);
                state.StringBuilder.Append(chunk);

                // here's our small protocol implementation: check the length-prefix
                string messageLengthText = Helpers.SubstringByDelimiter(state.StringBuilder, LengthPrefixDelimiter);
                if (state.TotalSize == 0)
                {
                    if (!string.IsNullOrEmpty(messageLengthText))
                    {
                        // get length and set total size
                        var messageLength = Convert.ToInt32(messageLengthText);
                        state.TotalSize = messageLength;
                    }
                    else
                    {
                        // if we haven't received the delimiter yet (very unlikely),
                        // simply continue reading
                        allRead = false;
                    }
                }

                // simply check if we've read all bytes
                allRead = allRead && state.StringBuilder.Length - messageLengthText.Length - 1 == state.TotalSize;
            }

            // check if we need to listen again
            if (!allRead)
            {
                // receive again
                socket.BeginReceive(state.Buffer,
                0,
                StateObject.BufferSize,
                0,
                ReceiveCallback,
                state);
            }
            else
            {
                // output anything we've received
                if (state.StringBuilder.Length > 0)
                {
                    // prepare result
                    string result = state.StringBuilder.ToString();
                    result = result.Substring(result.IndexOf(LengthPrefixDelimiter) + 1);

                    ParseData(result, socket);
                }
                else
                {
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();
                    Helpers.Logger("Closed client connection.", verbose);
                }
            }
        }

        private static void ParseData(string incomingData, Socket incomingSocket)
        {
            try
            {
                string[] dataIn = incomingData.Split(':');
                string[] parameters = dataIn[1].Split(';');

                string command = dataIn[0];

                switch (command)
                {
                    case "CONN":
                        PlayerObject playerObject = new PlayerObject();
                        playerObject.lastUpdated = DateTime.Now;
                        
                        connectedPlayers.Add(Helpers.RemoteEPtoIPEP(incomingSocket.RemoteEndPoint), playerObject);

                        playersLoggedIn = connectedPlayers.Count;
                        Helpers.Logger("New Player on game session:" + parameters[0], verbose); 
                        Helpers.Logger("Players in system: " + playersLoggedIn.ToString(), verbose);
                        break;

                    default:
                        Helpers.Logger("Received from: " + incomingSocket.RemoteEndPoint.ToString() + " - " + incomingData, verbose);
                        break;
                }
            }
            catch (IndexOutOfRangeException)
            {
                Helpers.Logger("Received malformed data", verbose);
            }
            catch (Exception ex)
            {
                Helpers.Logger(ex.Message, verbose);
            }

            incomingSocket.Shutdown(SocketShutdown.Both);
            incomingSocket.Close();
            Helpers.Logger("Closed client connection.", verbose);
        }

        private static void CleanUp()
        {
            while (serverRunning)
            {
                List<EndPoint> toDelete = new List<EndPoint>();
                Thread.Sleep(1000);

                foreach (IPEndPoint endPoint in connectedPlayers.Keys)
                {
                    if (DateTime.Now - connectedPlayers[endPoint].lastUpdated > TimeSpan.FromMinutes(1))
                    {
                        Helpers.Logger("Clean players", verbose);
                        toDelete.Add(endPoint);
                    }
                }

                foreach (IPEndPoint endpointKey in toDelete)
                {
                    connectedPlayers.Remove(endpointKey);
                }

                toDelete.Clear();

                int currentPlayers = connectedPlayers.Count;
                if (playersLoggedIn != currentPlayers)
                {
                    playersLoggedIn = currentPlayers;
                    Helpers.Logger("Players logged in: " + playersLoggedIn.ToString(), verbose);
                }
            }
        }
    }
    
}
